############# ########### ####### ##### ### ##
#
# Copyright (C) 2010 The Hursk Group, LLC
#
# This program is free software: you can redistribute it and/or modify 
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program (See file named COPYING).  If not, see 
# <http://www.gnu.org/licenses/>.
# 
############# ########### ####### ##### ### ##

#!/usr/bin/perl
package patchdiag;
use strict;
use English qw( -no_match_vars );
#eval 'exec /usr/bin/perl -S $0 ${1+"$@"}'
#    if $running_under_some_shell;
#			# this emulates #! processing on NIH machines.
#			# (remove #! line above if indigestible)

#$, = ' ';		# set output field separator
#$\ = "\n";		# set output record separator
$INPUT_RECORD_SEPARATOR = "\n"; #This was previously set to "\n\n"; in patchinfo_parser.pl

our %month;
$month{'Jan'} = '01';
$month{'Feb'} = '02';
$month{'Mar'} = '03';
$month{'Apr'} = '04';
$month{'May'} = '05';
$month{'Jun'} = '06';
$month{'Jul'} = '07';
$month{'Aug'} = '08';
$month{'Sep'} = '09';
$month{'Oct'} = '10';
$month{'Nov'} = '11';
$month{'Dec'} = '12';

#pass1 => "$dir/showrevp.out",
#pass2 => $pkgoutputfile,
#pass3 => "$dir/uname.out",
#pass4 => $patchdiag

sub process {
    my $args = shift;
    my %args = %{$args};#deref
    
    my %diag_patch_arr;
    my %diag_date_arr;
    my %diag_pkg_arr;
    
    # Solaris 8 patch listing.  Each line contains something like the following:
    # 112794-01
    
    # Output in CSV:
    # Needed patch, installed patch, status
    # Where status is either missing, old, current
    # missing: no patch was installed, but a security patch exists.  Might be a false positive
    # old: an older patch than the security patch was installed
    # current: the same or a newer patch was installed
    
    # Input: showrevp output
    
    my %patch_inst_arr;
    open FH, "<", $args{pass1} #pass1 => "$dir/showrevp.out"
	or die "Could not open the following pass1 file: ".$args{pass1};
    while (<FH>) {
	chomp;	# strip record separator
	my $line = $_;
	my @field = split(/\s+/, $line, -1);
	    
	# Output: patch_inst_arr[PATCH] = REV
	
	my $installedpatch = $field[1];
	# Record only the highest version in the array
	# showrev -p may contain more than one revision
	my @tmp_arr = split(/-/, $installedpatch, -1);
	my $patch = $tmp_arr[0];
	my $rev = $tmp_arr[1]; 
	if (exists($patch_inst_arr{$patch}) and $patch_inst_arr{$patch} <= $rev) {
	    $patch_inst_arr{$patch} = $rev;
	}
	elsif (not exists( $patch_inst_arr{$patch} ) ){
	    $patch_inst_arr{$patch} = $rev;
	}
    }
    close FH;
    
    # processed pkginfo long output
    # PKGINST:VERSION
    my %pkg_inst_arr;
    open FH, "<", $args{pass2} #pass2 => $pkgoutputfile
	or die "Could not open the following pass2 file: ".$args{pass2};
    while (<FH>) {
	chomp;
	my $line = $_;
	my @field = split(/\s+/, $line, -1);
	$field[1] =~ s/(^\s*)(\s*$)//;#no whitespace
	$pkg_inst_arr{$line} = $field[1];
    }
    close FH;
    
    my $my_platform;
    # uname
    open FH, "<", $args{pass3} #pass3 => "$dir/uname.out"
	or die "Could not open the following pass3 file: ".$args{pass3};
    while (<FH>) {
	chomp;
	my $line = $_;
	my @field = split(/\s+/, $line, -1);
	# 6: sparc, i386, etc.
	$my_platform = $field[5];
    }
    close FH;
    
    # Input patchdiag.xref
    open FH, "<", $args{pass4} #pass4 => $patchdiag
	or die "Could not open the following pass4 file: ".$args{pass4};
    LINES:
    while (<FH>) {
	chomp;
	my $line = $_;
	my @field = split(/\s+/, $line, -1);
	# Output is an array, pkg_arr:
	# pkg_arr[PKGINST:VERSION,counter] = PATCH-REV
	# E.g. PKGINST:VERSION = SUNWsrmr:1.2.0,REV=2000.03.20.10.37 
	# E.g. PATCH-REV = 100386-01
	
	# patchdiag is complex
	# XXX 
	#   We are not currently following obsolete security patches to 
	# make sure the updated patch is installed. If rev X has 15 
	# packages, but rev Y > X has 10 packages, we are only checking 
	# the packages with rev Y against the current rev (which could 
	# be anwhere between (1,X,Y) or greater than Y.  That is we will 
	# miss those 5 pkgs in rev X if say current rev is less than X, 
	# since we only check it against rev Y.  Let your mind wander on
	# this thought and think about the other scenarios that we're
	# also not getting...
	
	# Split up the patchdiag.xref file,
	# whose path was passed as the pass4 arg, and delimit it by '|'
	# 1: Patch
	# 2: Rev
	# 3: Date
	# 4: Recommened (R)
	# 5: Security (S)
	# 6: Obsolete (O)
	# 7: Y2K baby! (Y)
	# 8: OS, Unbundled, or ""
	# 9: platform
	# 10: package
	# 11: synopsis
	if ($line!~ /^##/) { #If this package entry is not commented out
	    my @tmp_arr = split(/\|/, $line, -1);
	    # Only care about non-obsolete security patches / packages first
	    #  on our hardware platform
	    if ($tmp_arr[4] and $tmp_arr[4] eq 'S'
		and $tmp_arr[5] ne 'O'
		and $tmp_arr[8] =~ $my_platform
		){
		# Have we been here? This will only return true if we have and if
		# the revision looked at is newer than the prior.
		my $patch = $tmp_arr[0];
		my $rev = $tmp_arr[1];
		# Split date
		my @datetime = split(/\//, $tmp_arr[2], -1);
		my $month_numeric;
		if ( exists( $datetime[0] ) ){
		    $month_numeric = $datetime[0] = $month{$datetime[0]}
		}
		else {
		    $month_numeric = '00';
		}
		my $day;
		if ( exists( $datetime[1] ) ){
		    $day = $datetime[1];
		}
		else {
		    $day = '00';
		}
		my $year;
		if (not exists($datetime[2]) or not $datetime[2]){
		    $datetime[0] = '00';
		    $datetime[1] = '00';
		    $datetime[2] = 0;
		}
		if ($datetime[2] and $datetime[2] <= 80) {
		    $year = 2000 + $datetime[2];
		}
		elsif ($datetime[2] == 0){
		    $year = '0000';
		}
		else {
		    $year = 1900 + $datetime[2];
		}
				    #month	#day
		my $date = $year . $month_numeric . $day;
		
		#Error-checking:
		if (not exists($args->{nonewerthan})
		    or not $args->{nonewerthan}
		    or $args->{nonewerthan} <= 0){
		    die "Invalid nonewerthan variable: ".$args->{nonewerthan};
		}
		
		## Skip patches newer than our nonewerthan variable
		if ($date > $args->{nonewerthan}){
		    next LINES;
		}
		
		my $BOOL_just_entered_patch_into_hash=0;
		if (not exists( $diag_patch_arr{$patch} ) ){	
		    $diag_patch_arr{$patch} = $rev;
		    $BOOL_just_entered_patch_into_hash=1;
		}
		
		if ($diag_patch_arr{$patch} < $rev
		    or $BOOL_just_entered_patch_into_hash == 1){	
		    $diag_patch_arr{$patch} = $rev;
		    $diag_date_arr{$patch} = $date;
		    # Then split the pkgarr string
		    my @tmp_pkg_arr;
		    #$tmp_arr[9] is something like this: SUNWcsu:11.5.1,REV=94.07.22.14.32,PATCH=383;
		    if ($tmp_arr[9] and $tmp_arr[9] !~ /^\s*$/) {
			$tmp_arr[9] =~ s/;\s*$//;#Take off ending semicolon
			my $packages_with_different_versions = $tmp_arr[9];
			PACKAGES:
			foreach my $package_with_version_info (split /;/, $packages_with_different_versions){
			    # Note: tmp_pkg_arr[j] == PKGINST:VERSION
			    # i.e. $pkg_ver == PKGINST:VERSION
			    my $pkg_and_ver = ($package_with_version_info =~ m/(.*?:.*?),/)[0];#Get the first match ( [0] ) of the first match ( $1 )
			    
			    if ($pkg_and_ver and $pkg_and_ver !~ /^\s*$/) {
				# Do we have an installed package?
				# Is it found anywhere in the %pkg_inst_arr?
				my $patch_ver_rev = ($package_with_version_info =~ m/(.*?:.*?)(,PATCH.*?).*?$/)[0];
				$patch_ver_rev = $package_with_version_info
				    if (not $patch_ver_rev or $patch_ver_rev =~ /^\s*$/);
				die "Could not regex patch_ver_rev"
				    if (not $patch_ver_rev or $patch_ver_rev =~ /^\s*$/);
				$patch_ver_rev =~ s/(^\s*)|(\s*$)//;#Take off whitespace
				
				if ( grep {$_ eq $patch_ver_rev} values %pkg_inst_arr ){
				    # If first time, add 
				    if (not exists( $diag_pkg_arr{$patch} )
					or $diag_pkg_arr{$patch} =~ /^\s*$/
					){
					$diag_pkg_arr{$patch} = $pkg_and_ver;
				    }
				    else {
					# else append
					$diag_pkg_arr{$patch} =
					$diag_pkg_arr{$patch} . "\t" .
					$pkg_and_ver;
				    }
				}
				else {
				    next PACKAGES;
				}
			    }
			    else {
				#Nothing
			    }
			}
		    }
		    else {
			next LINES;
		    }
		}
	    }
	}
    }
    close FH;
	
    # print out the following:
    # ip \t PKGINST \t PATCH \t Installed REV \t Security REV \t issue?
    # where issue can be:
    #  old
    #  current
    #  missing
    #  false positive (manually entered)
    # Does the package exist?
    
    # enumerate through all of the possible packages
    # which also happen to be installed
    
    my $return_string;
    foreach my $patch (keys %diag_pkg_arr) {
	my $req_rev = $diag_patch_arr{$patch};
	my $inst_rev = $patch_inst_arr{$patch};
	my $date = $diag_date_arr{$patch};
	my $issue;
	if (not $inst_rev or $inst_rev eq '') {
	    $inst_rev = '00';
	}
	if ($req_rev gt $inst_rev) {	#???
	    $issue = 'missing';
	}
	else {
	    $issue = 'current';
	}
	#XXX:
	
	my $ip = $args->{ip};
	
	#for ( pkg in tmp_pkg_arr ) {
	#print ip "\t" date "\t" patch "\t" req_rev "\t" inst_rev "\t"issue "\t" tmp_pkg_arr[pkg]
	
	$return_string .= $ip . ',' . $date . ',' . $patch . ',' . $req_rev . ',' . $inst_rev
	  . ',' . $issue . "\n";
	#}	
    }#foreach my $patch (keys %diag_pkg_arr)
    
    return $return_string;
}#sub process 

1;